(use-modules
 ;; for textual reading and writing procedures
 (ice-9 textual-ports)
 ;; not sure if needed
 (ice-9 binary-ports)
 ;; for `eof-object?`
 (ice-9 rdelim)
 ;; for keyword arguments
 (ice-9 optargs)
 ;; procedures for lists
 (srfi srfi-1)
  ;; unit testing
 (srfi srfi-64))

;; =================
;; HELPER PROCEDURES
;; =================

;; FILE HANDLING
(define* (get-lines-from-file file-path #:key (encoding "UTF-8"))
  ;; another common encoding is: "ISO-8859-1"
  ;; see http://www.iana.org/assignments/character-sets for more
  (define (get-lines-from-port port)
    (let ([line (get-line port)])
      (cond [(eof-object? line) '()]
            [else
             (cons line
                   (get-lines-from-port port))])))
  (call-with-input-file file-path
    (lambda (port)
      (set-port-encoding! port encoding)
      (get-lines-from-port port))))

(define (println output)
  (display (simple-format #f "~s\n" output)))

;; ========
;; SOLUTION
;; ========
;; data abstraction
(define (get-doubles-count res)
  (car res))
(define (get-tripples-count res)
  (cadr res))
(define (make-count-result doubles-count tripple-count)
  (list doubles-count tripple-count))

(define (calc-checksum doubles-count tripples-count)
  (* doubles-count tripples-count))

(define (count-occurrences-in-line line)
  (define initial-counts (make-hash-table))
  (define (iter counts remaining-string)
    (cond [(= (string-length remaining-string) 0) counts]
          [else
           ;; increase the count for this letter
           (hash-set! counts
                      (string-take remaining-string 1)
                      (+ (hash-ref counts (string-take remaining-string 1) 0) 1))
           ;; recur
           (iter counts (string-drop remaining-string 1))]))
  (iter initial-counts line))

(define (check-for-doubles-and-tripples line)
  (hash-fold (λ (key value prior-result)
               (list (or (= value 2) (car prior-result))
                     (or (= value 3) (cadr prior-result))))
             '(#f #f)
             (count-occurrences-in-line line)))

(define (count-doubles-and-tripples lines)
  (define (iter doubles-count tripples-count remaining-lines)
    (cond [(null? remaining-lines)
           (make-count-result doubles-count tripples-count)]
          [else
           (let ([line-result (check-for-doubles-and-tripples (car remaining-lines))])
             (iter (+ doubles-count (if (get-doubles-count line-result) 1 0))
                   (+ tripples-count (if (get-tripples-count line-result) 1 0))
                   (cdr remaining-lines)))]))
  (iter 0 0 lines))

(define (main args)
  (let* ([lines (get-lines-from-file (cadr args))]
         [count-result (count-doubles-and-tripples lines)]
         [checksum (calc-checksum (get-doubles-count count-result)
                                  (get-tripples-count count-result))])
    (println checksum)))

(main (program-arguments))


;; ==========
;; UNIT TESTS
;; ==========
(test-begin "day-02-part-01-test")
(test-group
 "day-02-part-01-test"
 (test-assert
     (let ([counts (count-occurrences-in-line "aabbccddeefffz")])
       (and (= (hash-ref counts "a" -1) 2)
            (= (hash-ref counts "b" -1) 2)
            (= (hash-ref counts "c" -1) 2)
            (= (hash-ref counts "d" -1) 2)
            (= (hash-ref counts "e" -1) 2)
            (= (hash-ref counts "f" -1) 3)
            (= (hash-ref counts "z" -1) 1)
            (= (hash-ref counts "y" -1) -1))))
 (test-equal (count-doubles-and-tripples '("aabbb" "aab" "abbb")) '(2 2))
 (test-equal (count-doubles-and-tripples '("abcc" "aab" "abbb")) '(2 1))
 (test-equal (check-for-doubles-and-tripples "aabccc") '(#t #t))
 (test-equal (check-for-doubles-and-tripples "aabcc") '(#t #f))
 (test-equal (check-for-doubles-and-tripples "abc") '(#f #f))
 (test-eq (calc-checksum 3 4) 12))
(test-end "day-02-part-01-test")
